package cn.mong.util.modelexcel;

import java.awt.Point;
import java.beans.PropertyDescriptor;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * excel导入工具类
 * @author mengxianming@2021-8-19 17:29:38
 *
 */
@Slf4j
public class ModelExcelImporter {
    private ExcelReader excelReader;
    
    public ModelExcelImporter(InputStream bookStream) {
        this.excelReader = ExcelUtil.getReader(bookStream);
    }
    
    public ModelExcelImporter(InputStream bookStream, int sheetIndex) {
        this.excelReader = ExcelUtil.getReader(bookStream, sheetIndex);
    }
    
    public ModelExcelImporter(InputStream bookStream, String sheetName) {
        this.excelReader = ExcelUtil.getReader(bookStream, sheetName);
    }
    

    /**
     * 从excel文件读取数据模型列表。
     * 
     * @param <D>
     * @param row
     * @param col
     * @param modelClazz
     * @return
     */
    public <D> List<D> readModels(int row, int col, Class<D> modelClazz) {
        List<D> result = new ArrayList<>();
        
        AnnotationModelHeaderResolver headerResolver = new AnnotationModelHeaderResolver(modelClazz);
        Point curPos = new Point(col, row);
        ReadSingleModelResult<D> smr = readSingleModel(curPos, modelClazz, headerResolver.getHierarchyHeaders());
        while(smr != null) {
            result.add(smr.model);
            curPos.y += smr.maxRows;
            smr = readSingleModel(curPos, modelClazz, headerResolver.getHierarchyHeaders());
        }
        
        return result;  
    }
    
    private <D> ReadSingleModelResult<D> readSingleModel(Point curPos, Class<D> modelClazz,
            List<HeaderCell> headers) {
        
        HeaderCell firstHeader = headers.stream()
                .filter(hc -> !hc.hasSub() && !isCollectionProperty(hc)).findFirst().get();
        PositionCell firstCellVal = readCellVal(curPos.x + firstHeader.getCol(), curPos.y);
        int maxRows = firstCellVal.getYspan();
        if(CheckUtil.isEmpty(firstCellVal.getVal()) && maxRows == 1) {
            return null;
        }
        
        Map<String, Object> dynaModel = new HashMap<>();
        headers.forEach(hc -> {
            boolean isCollectionProperty = isCollectionProperty(hc);

            List<Object> proValItems = new ArrayList<>();
            if(!hc.hasSub()) {
                Point subCurPos = new Point(curPos.x, curPos.y);
                while(subCurPos.y - curPos.y < maxRows) {
                    PositionCell proValItem = readCellVal(subCurPos.x + hc.getCol(), subCurPos.y);
                    subCurPos.y += proValItem.getYspan();
                    proValItems.add(proValItem.getVal());

                    if(!isCollectionProperty) {
                        break;
                    }
                }

            }else {
                Point subCurPos = new Point(curPos.x, curPos.y);
                while(subCurPos.y - curPos.y < maxRows) {
                    ReadSingleModelResult<?> proValRsmr = readSingleModel(subCurPos, hc.getSubModelClazz(), hc.getSubHeaders());
                    if(proValRsmr == null) {
                        break;
                    }

                    subCurPos.y += proValRsmr.maxRows;
                    proValItems.add(proValRsmr.model);

                    if(!isCollectionProperty) {
                        break;
                    }
                }
            }

            Object proVal = null;
            if(isCollectionProperty) {
                proVal = proValItems;
            }else {
                proVal = proValItems.isEmpty() ? null : proValItems.get(0);
            }     
            dynaModel.put(hc.getPropertyName(), proVal);
        });
        
        D model = JSONUtil.toBean(JSONUtil.toJsonStr(dynaModel), modelClazz);
        
        ReadSingleModelResult<D> result = new ReadSingleModelResult<>();
        result.model = model;
        result.maxRows = maxRows;
        return result;
    }

    private boolean isCollectionProperty(HeaderCell hc) {
        PropertyDescriptor pdesc;
        try {
            pdesc = BeanUtil.getPropertyDescriptor(hc.getModelClazz(), hc.getPropertyName());
        } catch (Exception e) {
            throw new IllegalStateException("获取类属性出错：" + hc.getModelClazz() + ", property=" + hc.getPropertyName() , e);
        }
        if(pdesc.getPropertyType().isArray() || Collection.class.isAssignableFrom(pdesc.getPropertyType())) {
            return true;
        }
        return false;
    }

    class ReadSingleModelResult<D>{
        D model;
        int maxRows;
    }
    

    
    /**
     * 获取单元格值
     *
     * @param x     列号，从0开始
     * @param y     行号，从0开始
     * @return 单元格合并信息
     */
    private PositionCell readCellVal(int x, int y) {
        Object val = excelReader.readCellValue(x, y);
        PositionCell result = getCellSpanInfo(x, y);
        result.setVal(val);
        return result;
    }
   
    /**
     * 单元格是否是合并单元格
     *
     * @param x     列号，从0开始
     * @param y     行号，从0开始
     * @return 单元格合并信息
     */
    private PositionCell getCellSpanInfo(int x, int y) {
        Sheet sheet = excelReader.getSheet();
        final int sheetMergeCount = sheet.getNumMergedRegions();
        CellRangeAddress ca;
        for (int i = 0; i < sheetMergeCount; i++) {
            ca = sheet.getMergedRegion(i);
            if (y >= ca.getFirstRow() && y <= ca.getLastRow()
                    && x >= ca.getFirstColumn() && x <= ca.getLastColumn()) {
                return new PositionCell(ca.getFirstRow(), ca.getFirstColumn(), null,
                        ca.getFirstColumn() - ca.getLastColumn() + 1, ca.getLastRow() - ca.getFirstRow() + 1, 0);
            }
        }
        
        return new PositionCell(y, x, null, 1, 1, 0);
    }

}
